#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <pthread.h>
#include <unistd.h>
#include "udk.h"
#include "pullsdk.h"
#include "platform.h"
#include "jni.h"
#include "bluetooth_interface.h"

//comment out following line to enable stdio redirecting
//#define ENABLE_STDIO_REDIRECT

#define FIND_CLASS(var, className) \
        var = env->FindClass(className); \
        UDK_LOGFATAL_IF(! var, "Unable to find class " className); \
        var = jclass(env->NewGlobalRef(var));

#define GET_STATIC_METHOD_ID(var, clazz, methodName, methodDescriptor) \
        var = env->GetStaticMethodID(clazz, methodName, methodDescriptor); \
        UDK_LOGFATAL_IF(! var, "Unable to find static method " methodName);

#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \
        var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \
        UDK_LOGFATAL_IF(! var, "Unable to find field " fieldName);

static JavaVM * g_jvm;
/* bluetooth module need this variable, so make it global */
JNIEnv * g_env = NULL;
jobject g_cbObj = NULL;
jclass g_jclsProcess = NULL;
jmethodID g_jmidProcess = NULL;



static const char *classPathName = "com/zkteco/coreservice/UdkService";

struct gBTCommClassInfo gBluetoothCommuClassInfo;

void ProcessJNICBDownloadProcess(int type, int size)
{
	if(NULL == g_cbObj)
		return;
	//JavaVM* pVm;
    //g_env->GetJavaVM(&pVm);
    //pVm->AttachCurrentThread(&g_env,NULL);
    if (NULL == g_jclsProcess)
    	g_jclsProcess = g_env->GetObjectClass(g_cbObj);
    if (g_jclsProcess == NULL)
    {
        return;
    }
	if (NULL == g_jmidProcess)
    	g_jmidProcess = g_env->GetMethodID(g_jclsProcess,"DownloadProcess","(II)V");
    if (g_jmidProcess == NULL)
    {
        return;
    }
    g_env->CallVoidMethod(g_cbObj,g_jmidProcess,type, size);
    //pVm->DetachCurrentThread();
}



#ifdef ENABLE_STDIO_REDIRECT
//the demo code that redirect stdout/stderr to android output
#ifndef LOG_TAG
#define LOG_TAG "UDK"
#endif
static int pfd[2];
static pthread_t thr;
static const char* tag=LOG_TAG;
static void * thread_func(void*){
	ssize_t rdsz;
	char buf[128];
	while((rdsz = read(pfd[0],buf,sizeof buf -1))>0){
		if(buf[rdsz-1]=='\n') buf[rdsz-1]='\0';//add null terminator
		__android_log_write(ANDROID_LOG_DEBUG,tag,buf);
	}
	return 0;
}
static int start_logger(const char* app_name){
	if(app_name)	tag = app_name;
	/*make stdout line-buffered and stderr unbuffered*/
	setvbuf(stdout,0,_IOLBF,0);
	setvbuf(stderr,0,_IONBF,0);
	/*create pipe*/
	pipe(pfd);
	dup2(pfd[1],1);
	dup2(pfd[1],2);

	/*spawn logging thread*/
	if(pthread_create(&thr,0,thread_func,0)==-1) return -1;
	pthread_detach(thr);
	return 0;
}
#endif
static struct {
    jclass clazz;
	jmethodID updatePullSdkListenerInfo;
}gPullSdkServiceClassInfo;

/*
static void PULLStatusCallback(void *user,int state)
{
	JNIEnv * env;
	int status;
	int handle = (int)user;
	status = g_jvm->GetEnv(&env,JNI_VERSION_1_4);
	if(status < 0){
        status = g_jvm->AttachCurrentThread(&env, NULL);
        if(status < 0) {
            UDK_LOGE("callback_handler: failed to attach "
                 "current thread");
            return;
        }
	}
	env->CallStaticVoidMethod(gPullSdkServiceClassInfo.clazz, 
	gPullSdkServiceClassInfo.updatePullSdkListenerInfo,
	handle,state);
	g_jvm->DetachCurrentThread();
	
}
*/

jlong JNI_UdkConnect(JNIEnv* env, jobject thiz, jintArray jprot,jstring jparam)
{
	UDK_HANDLE handle = NULL;
	const char *param = env->GetStringUTFChars(jparam,NULL);
	jint *prot = env->GetIntArrayElements(jprot, NULL);
	g_env = env;	
	handle = UdkConnect((int*)prot, param);
	env->ReleaseStringUTFChars(jparam, param);
	env->SetIntArrayRegion(jprot, 0, 1, prot);
	env->ReleaseIntArrayElements(jprot, prot, JNI_ABORT);
	return (jlong)handle;
}

void JNI_UdkDisconnect(JNIEnv* env, jobject thiz, jlong Handle)
{
	g_env = env;
	UdkDisconnect((UDK_HANDLE)Handle);
}

int JNI_UdkGetLastError(JNIEnv* env, jobject thiz, jlong Handle)
{
    g_env = env;
	return UdkGetLastError((UDK_HANDLE)Handle);
}


int	JNI_UdkSetLockState(JNIEnv* env, jobject thiz, jlong Handle,jint lock_no,jint opentime){
	g_env = env;
	return UdkSetLockState((UDK_HANDLE)Handle,lock_no,opentime);
}

int JNI_UdkGetLockState(JNIEnv* env, jobject thiz, jlong Handle,jint lock_no){
	g_env = env;
	return UdkGetLockState((UDK_HANDLE)Handle,lock_no);
}

int JNI_UdkGetLockCount(JNIEnv* env, jobject thiz, jlong Handle){
	g_env = env;
	return UdkGetLockCount((UDK_HANDLE)Handle);
}

int JNI_UdkSearchDevices(JNIEnv* env, jobject thiz, jstring jcommtype,jstring jaddress,jbyteArray jbuffer,jintArray jbuffersize )
{
	int ret;
	jbyte *buffer = (jbyte*)env->GetByteArrayElements(jbuffer, NULL);
	const char *commtype = (const char*)env->GetStringUTFChars(jcommtype,NULL);
	const char *address =  (const char*)env->GetStringUTFChars(jaddress,NULL);
	int *buffersize = env->GetIntArrayElements(jbuffersize, NULL);
	g_env = env;
	ret = UdkSearchDevices((char*)commtype,(char*)address,(char*)buffer,(int *)buffersize);
	env->ReleaseStringUTFChars(jcommtype, commtype);
	env->ReleaseStringUTFChars(jaddress, address);
	env->SetByteArrayRegion(jbuffer, 0, *buffersize, (jbyte*)buffer);
	env->SetIntArrayRegion(jbuffersize, 0, 1, buffersize);
	env->ReleaseIntArrayElements(jbuffersize, buffersize, JNI_ABORT);
	env->ReleaseByteArrayElements(jbuffer, buffer, JNI_ABORT);
	return ret;
}

jint JNI_UdkGetComPwd(JNIEnv* env, jobject thiz, jlong h, jbyteArray jbuffer, jintArray jbuffersize)
{
    int ret = 0;
    jbyte *buffer = (jbyte*)env->GetByteArrayElements(jbuffer, NULL);
    int *buffersize = env->GetIntArrayElements(jbuffersize, NULL);
    g_env = env;
    ret = UdkGetComPwd((UDK_HANDLE)h, (char*)buffer, buffersize);
    env->SetByteArrayRegion(jbuffer, 0, *buffersize, (jbyte*)buffer);
    env->SetIntArrayRegion(jbuffersize, 0, 1, buffersize);
    env->ReleaseIntArrayElements(jbuffersize, buffersize, JNI_ABORT);
	env->ReleaseByteArrayElements(jbuffer, buffer, JNI_ABORT);
    return ret;
}

jint JNI_UdkMobileAtt(JNIEnv* env, jobject thiz, jlong h, jint operate, jstring jpremeters, jbyteArray jbuffer, jintArray jbuffersize)
{
	int ret = 0;
	jbyte *buffer = (jbyte*)env->GetByteArrayElements(jbuffer, NULL);
	int *buffersize = env->GetIntArrayElements(jbuffersize, NULL);
	const char *premeters = (const char*)env->GetStringUTFChars(jpremeters,NULL);
	g_env = env;
	ret = UdkMobileAtt((UDK_HANDLE)h, operate, (char*)premeters, (char*)buffer, buffersize);
	env->ReleaseStringUTFChars(jpremeters, premeters);
	env->SetByteArrayRegion(jbuffer, 0, *buffersize, (jbyte*)buffer);
    env->SetIntArrayRegion(jbuffersize, 0, 1, buffersize);
    env->ReleaseIntArrayElements(jbuffersize, buffersize, JNI_ABORT);
	env->ReleaseByteArrayElements(jbuffer, buffer, JNI_ABORT);
	return ret;
}

jint JNI_UdkGetDeviceParam(JNIEnv* env, jobject thiz, jlong h, jbyteArray jbuffer, jint jbuffersize, jstring jitems)
{
	int ret = 0;
	jbyte *buffer = (jbyte*)env->GetByteArrayElements(jbuffer, NULL);
	const char *pitems = (const char*)env->GetStringUTFChars(jitems,NULL);
	g_env = env; 
	ret = UdkGetDeviceParam((UDK_HANDLE)h, (char*)buffer, (int)jbuffersize, (char*)pitems);
	env->ReleaseStringUTFChars(jitems, pitems);
	env->SetByteArrayRegion(jbuffer, 0, (int)jbuffersize, (jbyte*)buffer);
	env->ReleaseByteArrayElements(jbuffer, buffer, JNI_ABORT);
	return ret;
}

jint JNI_UdkSetDeviceParam(JNIEnv* env, jobject thiz, jlong h, jstring jitemandvalues)
{
	int ret = 0;
	const char *pitemandvalues = (const char*)env->GetStringUTFChars(jitemandvalues,NULL);
	g_env = env; 
	ret = UdkSetDeviceParam((UDK_HANDLE)h, (char*)pitemandvalues);
	env->ReleaseStringUTFChars(jitemandvalues, pitemandvalues);
	return ret;
}

jint JNI_UdkSetCallBack(JNIEnv* env, jobject thiz, jlong h, jobject job)
{
	int ret = 1;
	g_jclsProcess = NULL;
	g_jmidProcess = NULL;
	UdkSetDownloadProcessCallBack((UDK_HANDLE)h, (void *)ProcessJNICBDownloadProcess);
	g_env = env;
	g_cbObj = env->NewGlobalRef(job);
	return ret;
}

jint JNI_UdkResetCallBack(JNIEnv* env, jobject thiz, jlong h, jobject job)
{
	int ret = 1;
	UdkSetDownloadProcessCallBack((UDK_HANDLE)h, (void *)NULL);
	if(NULL != g_cbObj)
	{
		env->DeleteGlobalRef(g_cbObj);
		g_cbObj = NULL;
	}
	g_jclsProcess = NULL;
	g_jmidProcess = NULL;
	return ret;
}

jint JNI_UdkResetDeviceExt(JNIEnv* env, jobject thiz, jlong h, jstring jsparams)
{
	int ret = 0;
	const char *params = (const char*)env->GetStringUTFChars(jsparams,NULL);
	g_env = env; 
	ret = UdkResetDeviceExt((UDK_HANDLE)h, (char*)params);
	env->ReleaseStringUTFChars(jsparams, params);
	return ret;
}



static JNINativeMethod methods[] = { 
	{ "UdkConnect",			"([ILjava/lang/String;)J", 														
		(void*)JNI_UdkConnect }, 
	{ "UdkDisconnect",			"(J)V",			
		(void*)JNI_UdkDisconnect }, 
	{ "UdkGetLastError",			"(J)I",
		(void*)JNI_UdkGetLastError},
	{ "UdkSetLockState",		"(JII)I",	
		(void*)JNI_UdkSetLockState },
	{ "UdkGetLockState",	"(JI)I",						
		(void*)JNI_UdkGetLockState },
	{ "UdkGetLockCount",		"(J)I",
		(void*)JNI_UdkGetLockCount },
	{ "UdkSearchDevices",	"(Ljava/lang/String;Ljava/lang/String;[B[I)I",						
		(void*)JNI_UdkSearchDevices },
    { "UdkGetComPwd", "(J[B[I)I",
        (void*)JNI_UdkGetComPwd },
    { "UdkMobileAtt", "(JILjava/lang/String;[B[I)I",
        (void*)JNI_UdkMobileAtt },
    { "UdkGetDeviceParam",		"(J[BILjava/lang/String;)I",										
        (void*)JNI_UdkGetDeviceParam }, 
	{ "UdkSetDeviceParam",		"(JLjava/lang/String;)I",																
        (void *)JNI_UdkSetDeviceParam },
	{ "UdkSetCallBack",		"(JLcom/zkteco/coreservice/UdkService$CBInterface;)I",																
		(void *)JNI_UdkSetCallBack },
	{ "UdkResetCallBack",		"(JLcom/zkteco/coreservice/UdkService$CBInterface;)I",																
		(void *)JNI_UdkResetCallBack },
	{ "UdkResetDeviceExt",		"(JLjava/lang/String;)I",																
        (void *)JNI_UdkResetDeviceExt },

};

static int registerNativeMethods(JNIEnv* env, const char* className,
		JNINativeMethod* gMethods, int numMethods) {
	jclass clazz;

	g_env = env;
	clazz = env->FindClass(className);
	if (clazz == NULL) {
		return JNI_FALSE;
	}
	if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
		return JNI_FALSE;
	}

	FIND_CLASS(gPullSdkServiceClassInfo.clazz, "com/zkteco/coreservice/UdkService");

	#ifdef ENABLE_STDIO_REDIRECT
	start_logger(NULL);	
	#endif
	return JNI_TRUE;
}

static int registerNatives(JNIEnv* env) {
	if (!registerNativeMethods(env, classPathName, methods,
			sizeof(methods) / sizeof(methods[0]))) {
		return JNI_FALSE;
	}

	return JNI_TRUE;
}

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {

	jint result = -1;
	JNIEnv* env = NULL;
	
	g_jvm = vm;
	
	if (vm->GetEnv((void **)&env, JNI_VERSION_1_4) != JNI_OK) {
		goto bail;
	}

	if (registerNatives(env) != JNI_TRUE) {
		goto bail;
	}

	result = JNI_VERSION_1_4;

	bail: return result;
}

